﻿using Microsoft.Extensions.Logging;
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace HPSocket
{
    public class HPTcpServer : IDisposable
    {
        private Socket _socketServer;
        private int _connectedNumber;
        private readonly int _maxNumber;
        private readonly int _preNumber;
        private ILogger _logger;
        private SocketAsyncEventArgsPool _pool;

        public Action<SocketDataBuffer> AsyncReceivedDataCallback { get; set; }
        public Action<SocketAsyncEventArgs> AsyncEventArgsCallback { get; set; }
        public int Port { get; private set; }

        public HPTcpServer(ILogger logger, int listenPort, int initialCapacity, int maxCapacity)
        {
            Port = listenPort;
            _preNumber = 4;
            _maxNumber = maxCapacity;
            _logger = logger;
            _pool = new SocketAsyncEventArgsPool(initialCapacity, maxCapacity);
        }

        public void BeginListen()
        {
            if (_socketServer != null)
                return;

            _socketServer = new Socket(SocketType.Stream, ProtocolType.Tcp);
            EndPoint endPoint = new IPEndPoint(IPAddress.Any, Port);
            _socketServer.Bind(endPoint);
            _socketServer.Listen();

            /* 异步接受客户端 */
            //_socketServer.BeginAccept(AcceptAsyncCallback, _socketServer);

            /* 事件回调接受 */
            AcceptEventCallback();
        }

        #region 异步接收读取
        /// <summary>
        /// 异步接收客户端回调
        /// </summary>
        /// <param name="ar"></param>
        private void AcceptAsyncCallback(IAsyncResult ar)
        {
            Socket server = ar.AsyncState as Socket;
            Socket client = server.EndAccept(ar);
            server.BeginAccept(AcceptAsyncCallback, server);

            BeginReceiveAsync(client);
        }

        /// <summary>
        /// 客户端开始异步接收数据
        /// </summary>
        /// <param name="client"></param>
        private void BeginReceiveAsync(Socket client)
        {
            SocketDataBuffer dataBuffer = new SocketDataBuffer(client, 1024);
            dataBuffer.Client.BeginReceive(dataBuffer.Buffer, 0, dataBuffer.BufferSize, SocketFlags.None, TcpEndReceiveCallback, dataBuffer);
        }

        /// <summary>
        /// 客户异步接收数据回调
        /// </summary>
        /// <param name="ar"></param>
        private void TcpEndReceiveCallback(IAsyncResult ar)
        {
            try
            {
                var dataBuffer = ar.AsyncState as SocketDataBuffer;
                int readNum = dataBuffer.Client.EndReceive(ar);
                if (readNum == 0)
                {
                    ClientClosed(dataBuffer);
                    return;
                }
                dataBuffer.Offset = 0;
                dataBuffer.Length = readNum;
                AsyncReceivedDataCallback?.Invoke(dataBuffer);

                dataBuffer.Client.BeginReceive(dataBuffer.Buffer, 0, dataBuffer.BufferSize, SocketFlags.None, TcpEndReceiveCallback, dataBuffer);
            }
            catch (Exception ex)
            {
                _logger?.LogError(ex, "TcpEndReceiveCallback() Error:{{0}}");
            }
        }

        private void ClientClosed(SocketDataBuffer dataBuffer)
        {
            dataBuffer.Dispose();
        }

        #endregion

        #region 事件接收

        private void AcceptEventCallback()
        {
            if (_pool.CurrentNum - _connectedNumber >= _preNumber)
                return;

            if (_connectedNumber >= _maxNumber)
                return;

            var args = _pool.Rent();
            if (args == null)
                return;

            args.Completed += AcceptCompleted;
            _logger?.LogInformation($"{DateTime.Now}->新建了1个SocketAsyncEventArgs对象");
            _socketServer.AcceptAsync(args);
            AcceptEventCallback();
        }

        private void AcceptCompleted(object sender, SocketAsyncEventArgs args)
        {
            var client = args.AcceptSocket;
            if (client == null)
                return;

            switch (args.LastOperation)
            {
                case SocketAsyncOperation.Accept:
                    Interlocked.Increment(ref _connectedNumber);
                    AcceptEventCallback();
                    OnReceived(args);
                    _logger?.LogInformation($"{DateTime.Now}->{client.RemoteEndPoint} {args.LastOperation}");
                    break;
                case SocketAsyncOperation.Receive:
                case SocketAsyncOperation.ReceiveFrom:
                case SocketAsyncOperation.ReceiveMessageFrom:
                    if (args.BytesTransferred == 0)
                        OnRemoteDisconnected(args);
                    else
                        OnReceived(args);
                    break;
                case SocketAsyncOperation.Send:
                case SocketAsyncOperation.SendPackets:
                case SocketAsyncOperation.SendTo:
                    break;
            }
        }

        private void OnReceived(SocketAsyncEventArgs args)
        {
            var client = args.AcceptSocket;
            bool rcvRs = client.ReceiveAsync(args);
            if (rcvRs)
            {
                AsyncEventArgsCallback?.Invoke(args);
                //Span<byte> buf = args.MemoryBuffer.Span.Slice(args.Offset, args.BytesTransferred);
                //Console.WriteLine($"{DateTime.Now}->接收数据:{Encoding.UTF8.GetString(buf)}");
            }
        }

        private void OnRemoteDisconnected(SocketAsyncEventArgs args)
        {
            args.Completed -= AcceptCompleted;
            Interlocked.Decrement(ref _connectedNumber);
            Console.WriteLine($"{args.AcceptSocket.RemoteEndPoint}已断开");
            _pool.Return(args);
        }

        #endregion

        #region Dispose
        private bool disposedValue;

        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    // TODO: 释放托管状态(托管对象)
                }

                // TODO: 释放未托管的资源(未托管的对象)并重写终结器
                // TODO: 将大型字段设置为 null
                disposedValue = true;
            }
        }

        public void Dispose()
        {
            // 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中
            Dispose(disposing: true);
            GC.SuppressFinalize(this);
        }
        #endregion
    }

}